Skip to content

Comments

Allow promoted readonly properties to be reassigned once in constructor#20996

Open
nicolas-grekas wants to merge 9 commits intophp:masterfrom
nicolas-grekas:reinit-readonly-constructor
Open

Allow promoted readonly properties to be reassigned once in constructor#20996
nicolas-grekas wants to merge 9 commits intophp:masterfrom
nicolas-grekas:reinit-readonly-constructor

Conversation

@nicolas-grekas
Copy link
Contributor

@nicolas-grekas nicolas-grekas commented Jan 21, 2026

I find it quite annoying that readonly properties play badly with CPP (constructor property promotion).

Doing simple processing of any argument before assigning it to a readonly property forces opting out of CPP.

This PR allows setting once a readonly property in the body of a constructor after the property was previously set using CPP.

Before this PR (CPP not possible):

class Point {
    public readonly float $x;
    public readonly float $y;

    public function __construct(
        float $x = 0.0,
        float $y = 0.0,
    ) {
        $this->x = abs($x);
        $this->y = abs($y);
    }
}

After this PR:

class Point {
    public function __construct(
        public readonly float $x = 0.0,
        public readonly float $y = 0.0,
    ) {
        $this->x = abs($x);
        $this->y = abs($y);
    }
}

This allows keeping properties declaration in its compact form.

I'll submit an RFC of course but would also welcome early feedback here before.

@nicolas-grekas nicolas-grekas changed the title Allow promoted readonly property to be reassigned once in constructor Allow promoted readonly properties to be reassigned once in constructor Jan 21, 2026
@nicolas-grekas nicolas-grekas force-pushed the reinit-readonly-constructor branch 4 times, most recently from 1667954 to e8009a7 Compare January 22, 2026 14:35
@nicolas-grekas
Copy link
Contributor Author

Implementation is now ready.
Team work with Claude Code opus 4.5 💪
The CI is green (the failures are about CI tools that couldn't be installed, unrelated to the patch)

@nicolas-grekas
Copy link
Contributor Author

@nicolas-grekas nicolas-grekas force-pushed the reinit-readonly-constructor branch from e8009a7 to 3ccacde Compare January 22, 2026 17:13
@nicolas-grekas nicolas-grekas force-pushed the reinit-readonly-constructor branch from f36150c to e051b93 Compare February 2, 2026 21:15
Copy link
Member

@TimWolla TimWolla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A number of comments for the tests. Didn't yet look at all of them.

Replace the IS_PROP_CPP_REINITABLE property flag and IS_OBJ_CTOR_CALLED
object flag with the existing IS_PROP_REINITABLE flag, following the
same try/finally pattern already used by __clone().

How it works:
- On CPP implicit initialization, if the current execute frame's scope
  matches prop_info->ce (no stack walk needed), set IS_PROP_REINITABLE
  instead of clearing all property flags. This opens a one-shot
  reassignment window.
- zend_leave_helper clears IS_PROP_REINITABLE from all PROMOTED|READONLY
  properties of the constructor's scope on exit, gated on
  ZEND_CALL_HAS_THIS|ZEND_ACC_CTOR (covers both `new Foo()` and
  `parent::__construct()` calls, not just ZEND_CALL_RELEASE_THIS).
- zend_is_readonly_property_modifiable() simplified to a single
  IS_PROP_REINITABLE check; zend_is_in_declaring_constructor() and the
  call-stack walk helper removed entirely.

A repeated __construct() call cannot bypass readonly because the
property is already past IS_PROP_UNINIT, so IS_PROP_REINITABLE is never
re-set; no object-level tracking flag is needed.

This saves two flag bits (property and object extra_flags) and removes
~30 lines of stack-walk code.
@nicolas-grekas nicolas-grekas force-pushed the reinit-readonly-constructor branch from d5b3e4c to 67be986 Compare February 19, 2026 09:13
@nicolas-grekas nicolas-grekas force-pushed the reinit-readonly-constructor branch from 7a3ef4e to 003b60a Compare February 19, 2026 12:59
@nicolas-grekas nicolas-grekas force-pushed the reinit-readonly-constructor branch 2 times, most recently from 84228eb to 425081c Compare February 19, 2026 13:46
@nicolas-grekas nicolas-grekas force-pushed the reinit-readonly-constructor branch 2 times, most recently from b5c75b3 to 881a8e9 Compare February 19, 2026 14:36
@nicolas-grekas nicolas-grekas force-pushed the reinit-readonly-constructor branch from 881a8e9 to 74406a1 Compare February 19, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants